feat(daemon): local daemon owns the engine, DaemonClient default (M2.5)#218
Merged
Conversation
Contributor
|
🎉 PR Validation ✅ PASSED Commit: Checks:
Ready to merge! ✨ 🔗 View workflow run |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
M2.5: a local daemon becomes the default owner of the memory engine. One long-lived process serialises all vault mutations (no concurrent git index.lock collisions) and is the future host of the local MCP surface.
src/daemon/- loopback-onlynode:httpserver:GET /api/health({ok, version, pid, uptime, served}) +POST /api/memory/<verb>for the 6 frozen verbs, dispatching to ONE shared in-processMemoryClient(the samecreateDirectClientthe CLI uses). Engine is rebuilt only whenvaults.jsonchanges on disk, sovault addis picked up without a restart.src/daemon-entry.ts- the detached entrypoint: pidfile + port file under the config dir, graceful SIGTERM/SIGINT shutdown.src/lib/daemon-client.ts-DaemonClientimplements theMemoryClientinterface over HTTP;ensureDaemon()prefers a live daemon, autostarts one (detached spawn + health poll) when absent, and returns null on failure so verbs fall back to the in-processDirectClient.agentage daemon start|stop|status- status shows pid/port/uptime/served/version and warns on a bin-vs-daemon version mismatch with a restart hint (as doesagentage status).MemoryClientseam:resolveClient()= daemon by default,DirectClienton--no-daemon,AGENTAGE_NO_DAEMON=1, or when the daemon is unreachable and cannot be spawned. Fallback is silent and seamless.AGENTAGE_CONFIG_DIR+AGENTAGE_DAEMON_PORT, so tests and parallel setups never collide with a real daemon.Why
A single writer kills the git index.lock contention between concurrent clients of the same vault, and gives the upcoming local MCP surface a process to live in. The daemon and the direct fallback share the exact same engine code - the daemon is just its longest-lived caller.
Provenance
Ported from the archived v0.24 daemon in this repo's git history (pre-reboot): pidfile lifecycle, detached autostart + health poll, action dispatch, graceful stop,
daemonsubcommand. Stripped everything agent-runtime (agents, runs, schedules, websockets, machines, projects, settings, express) and adapted the vault actions to the memory-core router. One deliberate deviation from the harvested pattern:spawninstead offorkfor the detached start - fork always opens an IPC channel that keeps the parent event loop alive even afterunref(), which made the CLI hang afterdaemon start.R6 guard amendment
The package guard drops the now-obsolete strip markers
daemon-entry,ensure-daemon, and:4243(the daemon is a sanctioned feature again). The bans on express/ws/native sqlite and the exact runtime dep allowlist (@agentage/memory-core + chalk + commander + open) are unchanged - zero new dependencies.Tests
MemoryClient), DaemonClient round trip against an in-process server on an ephemeral port (never forks),ensureDaemonfallback matrix (mocked probe/spawn), pidfile/port lifecycle, daemon subcommands. 156 tests green, coverage 86/79/75/88.daemontier @p0 - explicitdaemon starton an OS-picked ephemeral port + isolated config dir, all 6 verbs routed through it (proven by the daemon'sservedcounter),daemon stopterminates the process and removes pid/port files; plus a--no-daemonbypass check. Existing offline tiers keep running against the in-process engine (AGENTAGE_NO_DAEMON=1in the harness) so they stay deterministic and never spawn background processes. Full suite 18/18 green locally.daemon start->memory write/searchvia the CLI (daemonservedcounter advanced) ->daemon stop-> process gone, pidfile gone.